Add pytest test suite and CI workflow - #13
Merged
Conversation
Adds a permanent, real pytest-based test suite for the previously untested YapHub codebase, replacing the throwaway python3 -c "..." mock scripts that had been used for one-off verification since the project started. Covers storage.py (full CRUD round-trips, the guarded _migrate() column-add path against a simulated pre-migration DB, idempotent double-initialize, panel_message_id lifecycle), services/ownership.py (the owner/admin/presence authorization matrix in _authorize_channel, including the exact deny-message differences), services/panel.py (Rename/Limit buttons refusing to open their modal on denial, refresh_panel_message's found/None/missing-record/NotFound paths), services/room_actions.py (apply_transfer/apply_claim calling refresh_panel_message exactly once after their ephemeral response, apply_claim's presence/owner-still-here/already-owns-another-room guards, apply_kick revoking a permit), services/temp_channels.py (reconcile_active_temp_channels' stale-guild/stale-channel/empty-room cleanup and panel_message_id backfill matrix, and the refcounted user_creation_locks eviction under concurrent asyncio.gather calls), a command-tree sanity check for the full /yap command surface, and a subprocess-based regression test for the services.panel / services.room_actions circular-import hazard that normal pytest collection order could otherwise mask. Also adds .github/workflows/ci.yml to run the suite on every push and pull_request, and requirements-dev.txt (pytest, pytest-asyncio, plus requirements.txt) for local runs. Only new files are added -- no existing commands/, services/, storage.py, bot.py, config.py, or schema.sql file is touched, to avoid conflicting with the independent feature branch developing against those same files in parallel. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9sZm6RFgrRL6z8Vy1HTLE
10 tasks
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
YapHub has never had an automated test suite — every change so far was verified with one-off
python3 -c "..."mock scripts that got thrown away after each PR. This adds a permanentpytestsuite plus a GitHub Actions workflow, without touching any existing source file.Scope discipline: this PR only adds new files (
tests/,.github/workflows/ci.yml,pytest.ini,requirements-dev.txt). No file undercommands/,services/,storage.py,bot.py,config.py, orschema.sqlis modified, to avoid conflicting with the independent feature branch developing against those same files in parallel.What's covered (82 tests)
storage.py(tests/test_storage.py) — full CRUD round-trips for profiles, active temp channels, and permits; the guarded_migrate()path exercised against a hand-built pre-migration database (missingdefault_user_limit/temp_name_template/panel_message_id) with existing rows, confirming columns are added and data survives; idempotent double-initialize();panel_message_idset/get and that it doesn't leak across a delete+recreate of the same channel id. Uses a real SQLite file viatempfile.TemporaryDirectory(), matching this repo's established manual-verification style (no DB mocking layer).services/ownership.py(tests/test_ownership.py) — the_authorize_channelmatrix: owner absent → allowed; non-owner+admin+present → allowed; non-owner+admin+absent → denied with the "must be connected" message; non-owner+non-admin → denied with the "owner or admin" message, even if present; untracked channel / wrong-guild record → denied. Also covers the guard clauses inresolve_owned_temp_channel/resolve_owned_temp_channel_by_id.services/panel.py(tests/test_panel.py) — Rename/Limit buttons don't open their modal when_resolvedenies (and do when it allows);refresh_panel_messageedits the message when found, no-ops whenpanel_message_idisNoneor the record is missing, and swallowsdiscord.NotFoundwithout raising.services/room_actions.py(tests/test_room_actions.py) —apply_transfer/apply_claimguard clauses (bot target, not-in-room, already-owns-another-room, owner-still-present for claim), and that both callrefresh_panel_messageexactly once, after (not before) the ephemeral response.apply_kickremoving a permit and revoking overwrites on success, and leaving both untouched when the move fails.services/temp_channels.py(tests/test_temp_channels.py) —reconcile_active_temp_channels's stale-guild / stale-channel / empty-room cleanup, and thepanel_message_idbackfill matrix (already set / owner present / owner left the guild); the refcounteduser_creation_locksdict is empty after 5 concurrentasyncio.gathercalls tocreate_temp_roomfor the same user.tests/test_command_tree.py) — builds a barediscord.ext.commands.Bot, wires upYapGroup, and asserts the full expected/yap ...command surface exists with no name collisions.tests/test_circular_import.py) — runsimport services.room_actions; import services.paneland the reverse order each in a fresh subprocess, to catch a regression in the deliberate lazy-import structure that normal pytest collection order could mask.tests/test_py_compile.py) —py_compile's every tracked.pyfile.Running locally
pytest.inisetsasyncio_mode = autoso async tests need no per-test marker. Mocking follows this repo's existing conventions (seetests/conftest.pydocstring):Mock(spec=discord.X)/AsyncMock()for discord.py objects,types.SimpleNamespacefor lightweightbot/storagestand-ins,unittest.mock.patchfor the lazily-importedrefresh_panel_message.CI
.github/workflows/ci.ymlruns on every push and pull request: checkout, Python 3.11,pip install -r requirements-dev.txt,pytest. No PR template exists in the repo (checked bothpull_request_template.mdand.github/PULL_REQUEST_TEMPLATE/).Test plan
pytestpasses locally: 82 passedgit diff --stat origin/main -- commands/ services/ storage.py bot.py config.py schema.sqlthat no existing source file is touched🤖 Generated with Claude Code
https://claude.ai/code/session_01V9sZm6RFgrRL6z8Vy1HTLE
Generated by Claude Code